﻿#region Using Statements

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Jitter;
using Jitter.Dynamics;
using Jitter.Collision;
using Jitter.LinearMath;
using Jitter.Collision.Shapes;
using Jitter.Dynamics.Constraints;
using Jitter.Dynamics.Joints;
using System.Reflection;
using System.Diagnostics;
using SingleBodyConstraints = Jitter.Dynamics.Constraints.SingleBody;
using System.IO;
using Jitter.DataStructures;
using VirtualBlocks.Enums;
using MoveFramework_CS;
using System.Threading;
using VirtualBlocks.Move_Framework_extension;

#endregion

namespace VirtualBlocks
{
    public class VirtualBlocks : Microsoft.Xna.Framework.Game
    {
        #region Private fields
        
        private GraphicsDeviceManager graphics;
        private Primitives3D.GeometricPrimitive[] primitives = new Primitives3D.GeometricPrimitive[4];
        private Random random = new Random();
        private Color backgroundColor = Color.Linen;
        //Current input states
        private KeyboardState keyState;
        private MouseState mouseState;
        private MoveButtonStates moveState;
        // Hold previous input states.
        private KeyboardState keyboardPreviousState = new KeyboardState();
        private MouseState mousePreviousState = new MouseState();
        private MoveButtonStates movePreviousState = new MoveButtonStates();

        #endregion

        #region Protected fields

        protected QuadDrawer quadDrawer;
        protected RasterizerState wireframe, cullMode, normal;
        // Store information for drag and drop
        protected JVector hitPoint, hitNormal;
        protected SingleBodyConstraints.PointOnPoint grabConstraint;
        protected RigidBody grabBody;
        protected float hitDistance = 0.0f;
        protected int scrollWheel = 0;

        #endregion

        #region Public fields

        public Camera Camera { private set; get; }
        public Display Display { private set; get; }
        public BasicEffect BasicEffect { private set; get; }
        public Scenes.EmptyScene PhysicScene { get; set; }
        public World World { private set; get; }

        #endregion

        public VirtualBlocks()
        {
            this.IsMouseVisible = true;
            Content.RootDirectory = "Content";
            
            graphics = new GraphicsDeviceManager(this);
            graphics.GraphicsProfile = GraphicsProfile.HiDef;
            graphics.PreferMultiSampling = true;
            graphics.PreferredBackBufferHeight = 800;
            graphics.PreferredBackBufferWidth = 1000;
            graphics.IsFullScreen = false;

            this.IsFixedTimeStep = false;
            this.graphics.SynchronizeWithVerticalRetrace = false;

            CollisionSystem collision = new CollisionSystemPersistentSAP();

            World = new World(collision); 
            World.AllowDeactivation = true;
                         
            wireframe = new RasterizerState();
            wireframe.FillMode = FillMode.WireFrame;

            cullMode = new RasterizerState();
            cullMode.CullMode = CullMode.None;
 
            normal = new RasterizerState();

            //PS Move init
            MoveWrapper.init();
            int movesCount = MoveWrapper.getMovesCount();
            if (movesCount > 0)
            {
                MoveWrapper.setRumble(0, 255);
                Thread.Sleep(500);
                MoveWrapper.setRumble(0, 0);
            }
            MoveWrapper.subscribeMoveUpdate(this.MoveUpdateCallback, this.MoveKeyDownCallback, this.MoveKeyUpCallback, this.NavUpdateCallback, this.NavKeyDownCallback, this.NavKeyUpCallback);
            Mouse.GetState();
            Keyboard.GetState();
            MoveWrapper.unsubscribeMove();
        }

        protected override void Initialize()
        {
            Camera = new Camera(this);
            Camera.Position = new Vector3(15, 15, 30);
            Camera.Target = Camera.Position + Vector3.Normalize(new Vector3(10, 5, 20));
            this.Components.Add(Camera);

            Display = new Display(this);
            Display.DrawOrder = int.MaxValue;
            this.Components.Add(Display);

            primitives[(int)Primitives.box] = new Primitives3D.BoxPrimitive(GraphicsDevice);
            primitives[(int)Primitives.cone] = new Primitives3D.ConePrimitive(GraphicsDevice);
            primitives[(int)Primitives.cylinder] = new Primitives3D.CylinderPrimitive(GraphicsDevice);
            primitives[(int)Primitives.sphere] = new Primitives3D.SpherePrimitive(GraphicsDevice);

            BasicEffect = new BasicEffect(GraphicsDevice);
            BasicEffect.EnableDefaultLighting();
            BasicEffect.PreferPerPixelLighting = true;
            
            this.PhysicScene = new Scenes.EmptyScene(this);

            if(PhysicScene != null)
                this.PhysicScene.Build();

            quadDrawer = new QuadDrawer(this.PhysicScene.Demo, 100);
            quadDrawer.Initialize();

            base.Initialize();
        }
        
        private Vector3 RayTo(int x, int y)
        {
            Vector3 nearSource = new Vector3(x, y, 0);
            Vector3 farSource = new Vector3(x, y, 1);

            Matrix world = Matrix.Identity;

            Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, Camera.Projection, Camera.View, world);
            Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, Camera.Projection, Camera.View, world);

            Vector3 direction = farPoint - nearPoint;

            return direction;
        }
        
        protected override void Update(GameTime gameTime)
        {       
            int moveId = 0;

            #region Registering states

            keyState = Keyboard.GetState();
            mouseState = Mouse.GetState();
            moveState = new MoveButtonStates(
                moveId,
                MoveWrapper.getButtonState(0, MoveButton.B_T1),
                MoveWrapper.getButtonState(0, MoveButton.B_T2),
                MoveWrapper.getButtonState(0, MoveButton.B_TRIANGLE),
                MoveWrapper.getButtonState(0, MoveButton.B_CIRCLE),
                MoveWrapper.getButtonState(0, MoveButton.B_CROSS),
                MoveWrapper.getButtonState(0, MoveButton.B_SQUARE),
                MoveWrapper.getButtonState(0, MoveButton.B_SELECT),
                MoveWrapper.getButtonState(0, MoveButton.B_START),
                MoveWrapper.getButtonState(0, MoveButton.B_STICK),
                MoveWrapper.getButtonState(0, MoveButton.B_UP),
                MoveWrapper.getButtonState(0, MoveButton.B_RIGHT),
                MoveWrapper.getButtonState(0, MoveButton.B_DOWN),
                MoveWrapper.getButtonState(0, MoveButton.B_LEFT),
                MoveWrapper.getButtonState(0, MoveButton.B_PS),
                MoveWrapper.getButtonState(0, MoveButton.B_MOVE),
                MoveWrapper.getButtonState(0, MoveButton.B_T),
                MoveWrapper.getPosition(moveId)
                );

            #endregion

            #region Keyboard/Buttons handling

            if (
                (!movePreviousState.IsStartPressed && moveState.IsStartPressed) ||
                keyState.IsKeyDown(Keys.Escape)
                )
                this.Exit();


            if (moveState.IsMovePressed)
            {
                this.SetMouseByMove(mouseState, moveState, 0.15);
            }

            if (keyboardPreviousState.IsKeyDown(Keys.R) && keyState.IsKeyUp(Keys.R))
            {
                var e = World.RigidBodies.GetEnumerator();
                e.MoveNext();
                e.MoveNext();
                World.RemoveBody(e.Current as RigidBody);
            }

            if (keyboardPreviousState.IsKeyDown(Keys.Space) && keyState.IsKeyUp(Keys.Space))
            {
                int rndn = rndn = random.Next(4);
                Shape shape = null;
                switch (rndn)
                {
                    case 0:
                        shape = new ConeShape(5f, 2f);
                        break;
                    case 1:
                        shape = new BoxShape(5f, 5f, 5f);
                        break;
                    case 2:
                        shape=new SphereShape(2f);
                        break;
                    case 3:
                        shape=new CylinderShape(6f, 1.5f);
                        break;
                }   
                
                DrawGivenPrimitive(shape);
            }

            if (
                (!movePreviousState.IsTrianglePressed && moveState.IsTrianglePressed) ||
                (keyboardPreviousState.IsKeyDown(Keys.F1) && keyState.IsKeyUp(Keys.F1))
               )
                DrawGivenPrimitive(new ConeShape(5f, 2f));

            if (
                (!movePreviousState.IsSquarePressed && moveState.IsSquarePressed)||
                (keyboardPreviousState.IsKeyDown(Keys.F2) && keyState.IsKeyUp(Keys.F2))
               )
                DrawGivenPrimitive(new BoxShape(5f, 5f, 5f));

            if (
                (!movePreviousState.IsCirclePressed && moveState.IsCirclePressed)||
                (keyboardPreviousState.IsKeyDown(Keys.F3) && keyState.IsKeyUp(Keys.F3))
               )
                DrawGivenPrimitive(new SphereShape(2f));

            if (
                (!movePreviousState.IsCrossPressed && moveState.IsCrossPressed)||
                (keyboardPreviousState.IsKeyDown(Keys.F4) && keyState.IsKeyUp(Keys.F4))
               )
                DrawGivenPrimitive(new CylinderShape(6f, 1.5f));
            
            #endregion

            #region Mouse left button/Move T button handling

            if ( 
                (!movePreviousState.IsTPressed && moveState.IsTPressed) ||
                (mouseState.LeftButton == ButtonState.Pressed && mousePreviousState.LeftButton == ButtonState.Released )
               )
            {
                JVector ray = Conversion.ToJitterVector(RayTo(mouseState.X, mouseState.Y));
                JVector camp = Conversion.ToJitterVector(Camera.Position);

                ray = JVector.Normalize(ray) * 100;

                float fraction;

                bool result = World.CollisionSystem.Raycast(camp, ray, RaycastCallback, out grabBody, out hitNormal, out fraction);

                if (result)
                {
                    hitPoint = camp + fraction * ray;

                    if (grabConstraint != null) 
                        World.RemoveConstraint(grabConstraint);

                    JVector lanchor = hitPoint - grabBody.Position;
                    lanchor = JVector.Transform(lanchor, JMatrix.Transpose(grabBody.Orientation));

                    grabConstraint = new SingleBodyConstraints.PointOnPoint(grabBody, lanchor);
                    grabConstraint.Softness = 0.01f;
                    grabConstraint.BiasFactor = 1.1f;
                    
                    World.AddConstraint(grabConstraint);
                    hitDistance = (Conversion.ToXNAVector(hitPoint) - Camera.Position).Length();

                    if(mouseState.LeftButton == ButtonState.Pressed)
                        scrollWheel = mouseState.ScrollWheelValue;
                    else if (moveState.IsTPressed)
                        scrollWheel = (int)(moveState.Position.z);

                    grabConstraint.Anchor = hitPoint;
                }
            }

            if (
                (moveState.IsTPressed)||
                (mouseState.LeftButton == ButtonState.Pressed)
               )
            {
                if (mouseState.LeftButton == ButtonState.Pressed)
                {
                    hitDistance += (mouseState.ScrollWheelValue - scrollWheel) * 0.01f;
                    scrollWheel = mouseState.ScrollWheelValue;
                }
                else if (moveState.IsTPressed)
                {
                    hitDistance += (moveState.Position.z - scrollWheel) * 0.01f;
                    scrollWheel = (int)moveState.Position.z;
                }

                if (grabBody != null)
                {
                    Vector3 ray = RayTo(mouseState.X, mouseState.Y); 
                    ray.Normalize();
                    grabConstraint.Anchor = Conversion.ToJitterVector(Camera.Position + ray * hitDistance);
                    grabBody.IsActive = true;
                    if (!grabBody.IsStatic)
                    {
                        grabBody.LinearVelocity *= 0.85f;
                        grabBody.AngularVelocity *=0.0f;// *= 0.98f;
                    }
                }
            }
            else
            {
                if (grabConstraint != null)
                    World.RemoveConstraint(grabConstraint);

                grabBody = null;
                grabConstraint = null;
            }

            #endregion

            #region Final operations

            float step = (float)gameTime.ElapsedGameTime.TotalSeconds;

            if (step > 1.0f / 100.0f)
                step = 1.0f / 100.0f;

            World.Step(step,false);
   
            keyboardPreviousState = keyState;
            mousePreviousState = mouseState;
            movePreviousState = moveState;

            this.UpdateText();
            base.Update(gameTime);

            #endregion
        }

        private bool RaycastCallback(RigidBody body, JVector normal, float fraction)
        {
            if (body.IsStatic)
                return false;
            else 
                return true;
        }

        private void DrawGivenPrimitive(Shape shape)
        {
            if (shape == null)
                return;

            JVector position = Conversion.ToJitterVector(new Vector3(
                    Camera.Target.X,
                    Camera.Target.Y,
                    -5));
            
            RigidBody body = new RigidBody(shape);
            
            Material material = new Material();
            material.KineticFriction = 1;
            material.StaticFriction = 2;
            material.Restitution = 1;
            
            body.Material = material;
            body.Position = position;
            body.IsActive = false;
            World.AddBody(body);            
        }
                
        private void AddShapeToDrawList(Shape shape, JMatrix ori, JVector pos)
        {
            Primitives3D.GeometricPrimitive primitive = null;
            Matrix scaleMatrix = Matrix.Identity;

            if (shape is BoxShape)
            {
                primitive = primitives[(int)Primitives.box];
                scaleMatrix = Matrix.CreateScale(Conversion.ToXNAVector((shape as BoxShape).Size));
            }
            else if (shape is SphereShape)
            {
                primitive = primitives[(int)Primitives.sphere];
                scaleMatrix = Matrix.CreateScale((shape as SphereShape).Radius);
            }
            else if (shape is CylinderShape)
            {
                primitive = primitives[(int)Primitives.cylinder];
                CylinderShape cs = shape as CylinderShape;
                scaleMatrix = Matrix.CreateScale(cs.Radius, cs.Height, cs.Radius);
            }
            else if (shape is ConeShape)
            {
                ConeShape cs = shape as ConeShape;
                scaleMatrix = Matrix.CreateScale(cs.Radius, cs.Height, cs.Radius);
                primitive = primitives[(int)Primitives.cone];
            }
            
            if(primitive != null)
                primitive.AddWorldMatrix(scaleMatrix * Conversion.ToXNAMatrix(ori) *
                        Matrix.CreateTranslation(Conversion.ToXNAVector(pos)));
        }

        private void AddBodyToDrawList(RigidBody rb)
        {
            if (rb.Tag is BodyTag && ((BodyTag)rb.Tag) == BodyTag.DontDrawMe) 
                return;
            //since we use only simple shapes no compound shape detection needed
            AddShapeToDrawList(rb.Shape, rb.Orientation, rb.Position);            
        }

        private void UpdateText()
        {
            if(moveState!= null && moveState.IsMovePressed)
                Display.DisplayText = "Move position: x=" + moveState.Position.x + " y=" + moveState.Position.y + " z=" + moveState.Position.z; 
            else
                Display.DisplayText = "Mouse position: x=" + Mouse.GetState().X + " y=" + Mouse.GetState().Y; 
        }

        private void SetMouseByMove(MouseState mouseState, Move_Framework_extension.MoveButtonStates moveState, double step)
        {
            if (
                mouseState.X < 0 || mouseState.X > Window.ClientBounds.Width
                || mouseState.Y < 0 || mouseState.Y > Window.ClientBounds.Height
                )
            {
                if (mouseState.X < 0)
                {
                    Mouse.SetPosition(10, mouseState.Y);
                }
                else if (mouseState.X > Window.ClientBounds.Width)
                {
                    Mouse.SetPosition(Window.ClientBounds.Width - 10, mouseState.Y);
                }

                if (mouseState.Y < 0)
                {
                    Mouse.SetPosition(mouseState.X, 10);
                }
                else if (mouseState.Y > Window.ClientBounds.Height)
                {
                    Mouse.SetPosition(mouseState.X, Window.ClientBounds.Height - 10);
                }
            }
            else
            {
                int posx = mouseState.X + (int)(moveState.Position.x * step);
                int posy = mouseState.Y - (int)(moveState.Position.y * step);
                Mouse.SetPosition(posx, posy);
            }
        }

        protected override void Draw(GameTime gameTime)
        {
            Matrix viewMatrix = Camera.View; 
            Vector3 right = new Vector3(viewMatrix.M11, viewMatrix.M21, viewMatrix.M31) * Display.AnaglyphDistance; //ofset from the center for each eye
            
            Matrix cameraRotation = Matrix.CreateRotationX(Camera.angles.X) * Matrix.CreateRotationY(Camera.angles.Y);
            Vector3 targetPos = Camera.Position + Vector3.Transform(Vector3.Forward, cameraRotation);
            Vector3 upVector = Vector3.Transform(Vector3.Up, cameraRotation);

            GraphicsDevice.SetRenderTarget(Display.leftEye);
           
            Matrix viewMatrixLeft = Matrix.CreateLookAt(Camera.Position - right, targetPos, Vector3.Up);  //up to be modified!!!
            Matrix viewMatrixRight = Matrix.CreateLookAt(Camera.Position + right, targetPos, Vector3.Up);
 
            GraphicsDevice.SetRenderTarget(Display.leftEye);
            DrawForEye(viewMatrixLeft, Camera.Projection,gameTime,false);

            GraphicsDevice.SetRenderTarget(Display.rightEye);
            
            DrawForEye(viewMatrixRight, Camera.Projection,gameTime,false);
            DrawForEye(viewMatrix, Camera.Projection,gameTime,true);

            GraphicsDevice.SetRenderTarget(null);

            Display.anaglyphEffect.Techniques["Anaglyphs"].Passes[0].Apply();
            Display.anaglyphEffect.Parameters["left"].SetValue(Display.leftEye);
            Display.anaglyphEffect.Parameters["right"].SetValue(Display.rightEye);
            
            Display.quad.DrawFullScreenQuad();

            base.Draw(gameTime);
         
            GraphicsDevice.RasterizerState = normal;
        }

        private void DrawForEye(Matrix viewMatrix, Matrix projection, GameTime g, Boolean isGround)
        {
            GraphicsDevice.Clear(backgroundColor);
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
           
            BasicEffect.View = viewMatrix;
            BasicEffect.Projection = projection;

            // Draw all shapes
            foreach (RigidBody body in World.RigidBodies)
            {
                if (body.Tag is int || body.IsParticle)
                    continue;

                AddBodyToDrawList(body);
            }
            
            if (isGround)
            {                
                quadDrawer.Draw(g);           
            }

            foreach (Primitives3D.GeometricPrimitive prim in primitives)
            {
                if (prim is Primitives3D.BoxPrimitive)
                    BasicEffect.DiffuseColor = Color.Black.ToVector3();
                else if (prim is Primitives3D.ConePrimitive)
                    BasicEffect.DiffuseColor = Color.Brown.ToVector3();
                else if (prim is Primitives3D.CylinderPrimitive)
                    BasicEffect.DiffuseColor = Color.Violet.ToVector3();
                else if (prim is Primitives3D.SpherePrimitive)
                    BasicEffect.DiffuseColor = Color.MidnightBlue.ToVector3();
                
                prim.Draw(BasicEffect);
            }
        }

        #region - Move Handling -

        void MoveUpdateCallback(int id, MoveWrapper.Vector3 position, MoveWrapper.Quaternion orientation, int trigger)
        {                   
        }

        void MoveKeyUpCallback(int id, int keyCode)
        {
        }

        void MoveKeyDownCallback(int id, int keyCode)
        {
        }

        void NavUpdateCallback(int id, int trigger1, int trigger2, int stickX, int stickY)
        {
        }

        void NavKeyUpCallback(int id, int keyCode)
        {
        }

        void NavKeyDownCallback(int id, int keyCode)
        {
        }

        #endregion
    }
}
